﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using JXT.PrimaryKey.Batman.Linq.Expressions;

namespace JXT.PrimaryKey.Batman.ObjectConverter
{
    /// <summary>
    /// 基于Lambda表达式树编译的对象转换器
    /// </summary>
    /// <typeparam name="TSource">原始对象类型</typeparam>
    /// <typeparam name="TResult">目标对象类型</typeparam>
    public class ExpressionObjectConverter<TSource, TResult> : IObjectConverter<TSource, TResult>
    {
        private static readonly object _synLock = new object();

        private Expression<Func<TSource, TResult>> _convertExpression;

        private ParameterExpression _parameterExpression;

        private ParameterExpression _resultExpression;

        private Queue<Expression> _assignExpressionQueue = new Queue<Expression>();

        private Func<TSource, TResult> _convertor;

        /// <summary>
        /// 默认构造方法
        /// </summary>
        public ExpressionObjectConverter()
        {
            _parameterExpression = Expression.Parameter(typeof(TSource), "e");
            _resultExpression = Expression.Parameter(typeof(TResult), "result");
            var newExpression = Expression.New(typeof(TResult));
            var initResultExpression = Expression.Assign(_resultExpression, newExpression);
            _assignExpressionQueue.Enqueue(initResultExpression);
        }

        /// <summary>
        /// 增加转换表达式
        /// </summary>
        /// <param name="resultPropertyExpression">结果转换表达式</param>
        /// <param name="sourcePropertyExpression">元转换表达式</param>
        /// <returns>转换器本身</returns>
        public IObjectConverter<TSource, TResult> Add(Expression resultPropertyExpression, Expression sourcePropertyExpression)
        {
            if (resultPropertyExpression is LambdaExpression)
                resultPropertyExpression = (resultPropertyExpression as LambdaExpression).Body;
            if (sourcePropertyExpression is LambdaExpression)
                sourcePropertyExpression = (sourcePropertyExpression as LambdaExpression).Body;
            ChangeParameterExpressionVisitor sourceConverter = new ChangeParameterExpressionVisitor();
            var realSourceBodyExpression = sourceConverter.Convert(sourcePropertyExpression, _parameterExpression);
            ChangeParameterExpressionVisitor resultObjectVisiter = new ChangeParameterExpressionVisitor();
            var realResultPropertyExpression = resultObjectVisiter.Convert(resultPropertyExpression, _resultExpression);
            var assignExpression = Expression.Assign(realResultPropertyExpression, realSourceBodyExpression);
            _assignExpressionQueue.Enqueue(assignExpression);
            return this;
        }

        /// <summary>
        /// 增加简单属性转换规则
        /// </summary>
        /// <typeparam name="TProperty">属性类型</typeparam>
        /// <param name="resultProperty">目标属性表达式</param>
        /// <param name="sourceProperty">原始属性表达式</param>
        /// <returns>转换器本身</returns>
        public IObjectConverter<TSource, TResult> Add<TProperty>(Expression<Func<TResult, TProperty>> resultPropertyExpression,
            Expression<Func<TSource, TProperty>> sourcePropertyExpression)
        {
            return Add(resultPropertyExpression as Expression, sourcePropertyExpression as Expression);
        }

        /// <summary>
        /// 增加复杂属性转换规则
        /// </summary>
        /// <typeparam name="TSourceProperty">原始属性类型</typeparam>
        /// <typeparam name="TResultProperty">目标属性类型</typeparam>
        /// <param name="resultPropertyExpression">目标属性表达式</param>
        /// <param name="sourcePropertyExpression">原始属性表达式</param>
        /// <param name="converter">复杂属性转换器</param>
        /// <returns>转换器本身</returns>
        public IObjectConverter<TSource, TResult> Add<TSourceProperty, TResultProperty>(Expression<Func<TResult, TResultProperty>> resultPropertyExpression, 
            Expression<Func<TSource, TSourceProperty>> sourcePropertyExpression,
            IObjectConverter<TSourceProperty, TResultProperty> converter)
        {
            AddConverterCall(resultPropertyExpression, sourcePropertyExpression, converter, "Convert");
            return this;
        }

        /// <summary>
        /// 增加复杂属性转换规则
        /// </summary>
        /// <typeparam name="TSourceProperty">原始属性类型</typeparam>
        /// <typeparam name="TResultProperty">目标属性类型</typeparam>
        /// <param name="resultPropertyExpression">目标属性表达式</param>
        /// <param name="sourcePropertyExpression">原始属性表达式</param>
        /// <param name="converter">对象转换表达式</param>
        /// <returns>转换器本身</returns>
        public IObjectConverter<TSource, TResult> Add<TSourceProperty, TResultProperty>(Expression<Func<TResult, TResultProperty>> resultPropertyExpression,
            Expression<Func<TSource, TSourceProperty>> sourcePropertyExpression,
            Expression<Func<TSourceProperty, TResultProperty>> converter)
        {
            var fun = converter.Compile();
            return AddFunction(resultPropertyExpression, sourcePropertyExpression, fun);
        }

        /// <summary>
        /// 增加复杂属性转换规则
        /// </summary>
        /// <typeparam name="TSourceProperty">原始属性类型</typeparam>
        /// <typeparam name="TResultProperty">目标属性类型</typeparam>
        /// <param name="resultPropertyExpression">目标属性表达式</param>
        /// <param name="sourcePropertyExpression">原始属性表达式</param>
        /// <param name="converter">对象转换方法委托</param>
        /// <returns>转换器本身</returns>
        public IObjectConverter<TSource, TResult> AddFunction<TSourceProperty, TResultProperty>(Expression<Func<TResult, TResultProperty>> resultPropertyExpression,
            Expression<Func<TSource, TSourceProperty>> sourcePropertyExpression,
            Func<TSourceProperty, TResultProperty> converter)
        {
            AddConverterCall(resultPropertyExpression, sourcePropertyExpression, converter, "Invoke");
            return this;
        }

        /// <summary>
        /// 增加复杂属性集合转换规则
        /// </summary>
        /// <typeparam name="TSourceProperty">原始属性类型</typeparam>
        /// <typeparam name="TResultProperty">目标属性类型</typeparam>
        /// <param name="resultPropertyExpression">目标属性表达式</param>
        /// <param name="sourcePropertyExpression">原始属性表达式</param>
        /// <param name="converter">复杂属性集合转换器</param>
        /// <returns>转换器本身</returns>
        public IObjectConverter<TSource, TResult> AddEnumerable<TSourceProperty, TResultProperty>(Expression<Func<TResult, IEnumerable<TResultProperty>>> resultPropertyExpression,
            Expression<Func<TSource, IEnumerable<TSourceProperty>>> sourcePropertyExpression, 
            IObjectConverter<TSourceProperty, TResultProperty> converter)
        {
            AddConverterCall(resultPropertyExpression, sourcePropertyExpression, converter, "ConvertList");
            return this;
        }

        /// <summary>
        /// 增加复杂属性集合转换规则
        /// </summary>
        /// <typeparam name="TSourceProperty">原始属性类型</typeparam>
        /// <typeparam name="TResultProperty">目标属性类型</typeparam>
        /// <param name="resultPropertyExpression">目标属性表达式</param>
        /// <param name="sourcePropertyExpression">原始属性表达式</param>
        /// <param name="converter">属性集合转换表达式</param>
        /// <returns>转换器本身</returns>
        public IObjectConverter<TSource, TResult> AddEnumerable<TSourceProperty, TResultProperty>(Expression<Func<TResult, IEnumerable<TResultProperty>>> resultPropertyExpression,
            Expression<Func<TSource, IEnumerable<TSourceProperty>>> sourcePropertyExpression, 
            Expression<Func<IEnumerable<TSourceProperty>, IEnumerable<TResultProperty>>> converter)
        {
            var fun = converter.Compile();
            AddEnumerableFunction(resultPropertyExpression, sourcePropertyExpression, fun);
            return this;
        }

        /// <summary>
        /// 增加复杂属性集合转换规则
        /// </summary>
        /// <typeparam name="TSourceProperty">原始属性类型</typeparam>
        /// <typeparam name="TResultProperty">目标属性类型</typeparam>
        /// <param name="resultPropertyExpression">目标属性表达式</param>
        /// <param name="sourcePropertyExpression">原始属性表达式</param>
        /// <param name="converter">属性集合转换方法委托</param>
        /// <returns>转换器本身</returns>
        public IObjectConverter<TSource, TResult> AddEnumerableFunction<TSourceProperty, TResultProperty>(Expression<Func<TResult, IEnumerable<TResultProperty>>> resultPropertyExpression,
            Expression<Func<TSource, IEnumerable<TSourceProperty>>> sourcePropertyExpression, 
            Func<IEnumerable<TSourceProperty>, IEnumerable<TResultProperty>> converter)
        {
            AddConverterCall(resultPropertyExpression, sourcePropertyExpression, converter, "Invoke");
            return this;
        }

        /// <summary>
        /// 编译表达式
        /// </summary>
        public void Compile()
        {
            var expressionList = _assignExpressionQueue.ToList();
            expressionList.Add(_resultExpression);
            var blockExpression = Expression.Block(new[] { _resultExpression }, expressionList);
            _convertExpression = Expression.Lambda<Func<TSource, TResult>>(blockExpression, _parameterExpression);
            _convertor = _convertExpression.Compile();
        }

        /// <summary>
        /// 执行转换对象操作
        /// </summary>
        /// <param name="source">原始对象</param>
        /// <returns>目标对象</returns>
        public TResult Convert(TSource source)
        {
            if (source == null)
                return default(TResult);
            if (_convertor == null)
            {
                lock (_synLock)
                {
                    if (_convertor == null)
                    {
                        Compile();
                    }
                }
            }
            return _convertor(source);
        }

        /// <summary>
        /// 转换集合
        /// </summary>
        /// <param name="enumerable">原始类型集合</param>
        /// <returns>转换后集合</returns>
        public IEnumerable<TResult> ConvertEnumerable(IEnumerable<TSource> enumerable)
        {
            if (enumerable == null || enumerable.Count() <= 0)
                return new List<TResult>();
            return enumerable.Select(e => Convert(e));
        }

        /// <summary>
        /// 转换List对象
        /// </summary>
        /// <param name="sourceEnumerable">原始类型List</param>
        /// <returns>转换后List</returns>
        public List<TResult> ConvertList(IEnumerable<TSource> sourceEnumerable)
        {
            return ConvertEnumerable(sourceEnumerable).ToList();
        }

        protected virtual void AddConverterCall(Expression resultPropertyExpression,
            Expression sourcePropertyExpression,
            object converter,
            string methodName)
        {
            if (resultPropertyExpression is LambdaExpression)
                resultPropertyExpression = (resultPropertyExpression as LambdaExpression).Body;
            if (sourcePropertyExpression is LambdaExpression)
                sourcePropertyExpression = (sourcePropertyExpression as LambdaExpression).Body;
            ChangeParameterExpressionVisitor sourceConverter = new ChangeParameterExpressionVisitor();
            var realSourceBodyExpression = sourceConverter.Convert(sourcePropertyExpression, _parameterExpression);
            ChangeParameterExpressionVisitor resultObjectVisiter = new ChangeParameterExpressionVisitor();
            var realResultPropertyExpression = resultObjectVisiter.Convert(resultPropertyExpression, _resultExpression);
            Expression converterExpression = Expression.Constant(converter);
            MethodInfo convertMethod = converter.GetType().GetMethod(methodName);
            var convertCallExpression = Expression.Call(converterExpression, convertMethod, realSourceBodyExpression);
            var assignExpression = Expression.Assign(realResultPropertyExpression, convertCallExpression);
            _assignExpressionQueue.Enqueue(assignExpression);
        }
    }
}
